Skip to content

[FEATURE] Update cachemanager and cacherepository implementations to async#715

Merged
guibranco merged 24 commits into
guibranco:mainfrom
HarryKambo:feature/cacheasync
Jul 4, 2026
Merged

[FEATURE] Update cachemanager and cacherepository implementations to async#715
guibranco merged 24 commits into
guibranco:mainfrom
HarryKambo:feature/cacheasync

Conversation

@HarryKambo

@HarryKambo HarryKambo commented Jun 27, 2025

Copy link
Copy Markdown
Contributor

User description

Closes #88

πŸ“‘ Description

βœ… Checks

  • My pull request adheres to the code style of this project
  • My code requires changes to the documentation
  • I have updated the documentation as required
  • All the tests have passed

☒️ Does this introduce a breaking change?

  • Yes
  • No

β„Ή Additional Information

Summary by Sourcery

Convert cache management APIs and repository implementations to asynchronous patterns, adding cancellation support, timeouts, and enhanced logging throughout the caching layers, and update dependent tests and SmtpMailer to use the new async methods.

New Features:

  • Support async cache operations in CacheManager with CancellationToken and default timeout logic.

Enhancements:

  • Refactor ICacheRepository to define async ValueTask/Task methods.
  • Update MemoryCacheRepository, RedisCacheRepository, and CouchDBCacheRepository to implement the new async API with cancellation tokens, exception handling, and logging.
  • Enhance CacheManager to run repository calls in parallel, track success counts, handle exceptions per repository, and promote retrieved values to memory cache.
  • Introduce default timeout for cache Get calls and unify cancellation handling in all cache operations.
  • Modify SmtpMailer to use async cache methods for error throttling.

Tests:

  • Revise cache-related tests to use the async API with await, renaming methods to SetAsync/GetAsync/TryGetAsync, and update assertions accordingly.

Note

I'm currently writing a description for your pull request. I should be done shortly (<1 minute). Please don't edit the description field until I'm finished, or we may overwrite each other. If I find nothing to write about, I'll delete this message.


Description

  • Migrated the entire caching layer to asynchronous APIs, enhancing performance and responsiveness.
  • Introduced cancellation token support across all cache operations to allow for better control during long-running tasks.
  • Improved logging and error handling mechanisms to provide better insights during cache operations.
  • Updated all relevant repository classes and interfaces to support the new asynchronous patterns.
  • Refactored tests to ensure they validate the new asynchronous behavior and cancellation handling.

Changes walkthrough πŸ“

Relevant files
Enhancement
CacheManager.cs
Migrate CacheManager to Asynchronous OperationsΒ  Β  Β  Β  Β  Β  Β  Β  Β  Β 

Src/CrispyWaffle/Cache/CacheManager.cs

  • Migrated cache operations to asynchronous methods.
  • Added cancellation token support for cache operations.
  • Improved logging and error handling in cache methods.
  • +732/-162
    CouchDBCacheRepository.cs
    Enhance CouchDBCacheRepository with Async SupportΒ  Β  Β  Β  Β  Β  Β  Β 

    Src/CrispyWaffle.CouchDB/Cache/CouchDBCacheRepository.cs

  • Updated methods to support asynchronous operations.
  • Added cancellation token support for database operations.
  • +279/-92
    RedisCacheRepository.cs
    Refactor RedisCacheRepository for Asynchronous Operations

    Src/CrispyWaffle.Redis/Cache/RedisCacheRepository.cs

  • Refactored methods to be asynchronous.
  • Integrated cancellation token for Redis operations.
  • +362/-58
    MemoryCacheRepository.cs
    Update MemoryCacheRepository for Async MethodsΒ  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β 

    Src/CrispyWaffle/Cache/MemoryCacheRepository.cs

  • Converted cache methods to asynchronous.
  • Implemented cancellation token handling.
  • +109/-29
    ICacheRepository.cs
    Modify ICacheRepository for Async Method SupportΒ  Β  Β  Β  Β  Β  Β  Β  Β 

    Src/CrispyWaffle/Cache/ICacheRepository.cs

  • Updated interface to include asynchronous method signatures.
  • Added cancellation token parameters to methods.
  • +26/-12Β 
    CacheManagerTests.cs
    Refactor CacheManager Tests for Asynchronous MethodsΒ  Β  Β  Β  Β 

    Tests/CrispyWaffle.Tests/Cache/CacheManagerTests.cs

  • Updated tests to use asynchronous methods.
  • Ensured cancellation tokens are utilized in tests.
  • +35/-32Β 
    MemoryCacheRepositoryTests.cs
    Update MemoryCacheRepository Tests for Async SupportΒ  Β  Β  Β  Β 

    Tests/CrispyWaffle.Tests/Cache/MemoryCacheRepositoryTests.cs

  • Converted tests to support asynchronous operations.
  • Ensured proper handling of cancellation tokens in tests.
  • +12/-11Β 

    πŸ’‘ Penify usage:
    Comment /help on the PR to get a list of all available Penify tools and their descriptions

    Summary by CodeRabbit

    Summary by CodeRabbit

    • New Features

      • Cache operations for memory, Redis, and CouchDB are now fully asynchronous with cancellation support, including set, get, remove, clear, and TTL.
      • CacheManager now supports async reads/writes with optional timeout behavior.
    • Bug Fixes

      • Improved cancellation/exception handling across cache access paths, including clearer cancellation behavior and more resilient execution flow.
    • Tests

      • Updated cache-related integration and unit tests to use async/await and the new async APIs.

    @sourcery-ai

    sourcery-ai Bot commented Jun 27, 2025

    Copy link
    Copy Markdown

    Reviewer's Guide

    This PR overhauls the caching layer by migrating all CacheManager methods, repository interfaces, and implementations to asynchronous APIs with CancellationToken support, built-in default timeouts, enhanced logging and error handling, and automatic promotion of fetched entries into the in-memory cache. Tests have been updated to exercise the new async methods.

    Sequence diagram for async GetAsync with automatic memory cache promotion

    sequenceDiagram
        participant Client
        participant CacheManager
        participant MemoryCacheRepository
        participant RedisCacheRepository
        participant CouchDBCacheRepository
        Client->>CacheManager: GetAsync<T>(key, cancellationToken)
        CacheManager->>MemoryCacheRepository: GetAsync<T>(key, cancellationToken)
        alt Found in memory
            MemoryCacheRepository-->>CacheManager: value
            CacheManager-->>Client: value
        else Not found in memory
            CacheManager->>RedisCacheRepository: GetAsync<T>(key, cancellationToken)
            alt Found in Redis
                RedisCacheRepository-->>CacheManager: value
                CacheManager->>MemoryCacheRepository: SetAsync<T>(value, key, CancellationToken.None)
                CacheManager-->>Client: value
            else Not found in Redis
                CacheManager->>CouchDBCacheRepository: GetAsync<T>(key, cancellationToken)
                alt Found in CouchDB
                    CouchDBCacheRepository-->>CacheManager: value
                    CacheManager->>MemoryCacheRepository: SetAsync<T>(value, key, CancellationToken.None)
                    CacheManager-->>Client: value
                else Not found in any
                    CouchDBCacheRepository-->>CacheManager: (throws)
                    CacheManager-->>Client: (throws InvalidOperationException)
                end
            end
        end
    
    Loading

    Class diagram for updated ICacheRepository interface and implementations

    classDiagram
        class ICacheRepository {
            <<interface>>
            +ValueTask SetAsync<T>(T value, string key, TimeSpan? ttl = null, CancellationToken cancellationToken = default)
            +ValueTask SetAsync<T>(T value, string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<(bool Success, T Value)> TryGetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<(bool Success, T Value)> TryGetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +Task RemoveAsync(string key, CancellationToken cancellationToken = default)
            +Task RemoveAsync(string key, string subKey, CancellationToken cancellationToken = default)
            +Task<TimeSpan> TTLAsync(string key, CancellationToken cancellationToken = default)
            +Task Clear()
        }
        class MemoryCacheRepository {
            +ValueTask SetAsync<T>(T value, string key, TimeSpan? ttl = null, CancellationToken cancellationToken = default)
            +ValueTask SetAsync<T>(T value, string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<(bool Success, T Value)> TryGetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<(bool Success, T Value)> TryGetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +Task RemoveAsync(string key, CancellationToken cancellationToken = default)
            +Task RemoveAsync(string key, string subKey, CancellationToken cancellationToken = default)
            +Task<TimeSpan> TTLAsync(string key, CancellationToken cancellationToken = default)
            +Task Clear()
        }
        class RedisCacheRepository {
            +ValueTask SetAsync<T>(T value, string key, TimeSpan? ttl = null, CancellationToken cancellationToken = default)
            +ValueTask SetAsync<T>(T value, string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<(bool Success, T Value)> TryGetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<(bool Success, T Value)> TryGetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +Task RemoveAsync(string key, CancellationToken cancellationToken = default)
            +Task RemoveAsync(string key, string subKey, CancellationToken cancellationToken = default)
            +Task<TimeSpan> TTLAsync(string key, CancellationToken cancellationToken = default)
            +Task Clear()
        }
        class CouchDBCacheRepository {
            +ValueTask SetAsync<T>(T value, string key, TimeSpan? ttl = null, CancellationToken cancellationToken = default)
            +ValueTask SetAsync<T>(T value, string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<(bool Success, T Value)> TryGetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<(bool Success, T Value)> TryGetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +Task RemoveAsync(string key, CancellationToken cancellationToken = default)
            +Task RemoveAsync(string key, string subKey, CancellationToken cancellationToken = default)
            +Task<TimeSpan> TTLAsync(string key, CancellationToken cancellationToken = default)
            +Task Clear()
        }
        ICacheRepository <|.. MemoryCacheRepository
        ICacheRepository <|.. RedisCacheRepository
        ICacheRepository <|.. CouchDBCacheRepository
    
    Loading

    Class diagram for updated CacheManager async API

    classDiagram
        class CacheManager {
            <<static>>
            +ValueTask SetAsync<T>(T value, string key, CancellationToken cancellationToken = default)
            +ValueTask SetAsync<T>(T value, string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask SetAsync<T>(T value, string key, TimeSpan ttl, CancellationToken cancellationToken = default)
            +ValueTask SetToAsync<TCacheRepository, TValue>(TValue value, string key, CancellationToken cancellationToken = default)
            +ValueTask SetToAsync<TCacheRepository, TValue>(TValue value, string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask SetToAsync<TCacheRepository, TValue>(TValue value, string key, TimeSpan ttl, CancellationToken cancellationToken = default)
            +ValueTask SetToAsync<TCacheRepository, TValue>(TValue value, string key, string subKey, TimeSpan ttl, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<T> GetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<TValue> GetFrom<TCacheRepository, TValue>(string key, CancellationToken cancellationToken = default)
            +ValueTask<TValue> GetFromAsync<TCacheRepository, TValue>(string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask<(bool Success, T Value)> TryGetAsync<T>(string key, CancellationToken cancellationToken = default)
            +ValueTask<bool> TryGetAsync<T>(string key, string subKey, CancellationToken cancellationToken = default)
            +Task<TimeSpan> TTLAsync(string key)
            +ValueTask Remove(string key, CancellationToken cancellationToken = default)
            +ValueTask Remove(string key, string subKey, CancellationToken cancellationToken = default)
            +ValueTask RemoveFrom<TCacheRepository>(string key, CancellationToken cancellationToken = default)
            +ValueTask RemoveFrom<TCacheRepository>(string key, string subKey, CancellationToken cancellationToken = default)
        }
    
    Loading

    File-Level Changes

    Change Details Files
    Migrate CacheManager to async/ValueTask with cancellation and timeout handling
    • Convert all CacheManager methods from sync to async/ValueTask signatures with optional CancellationToken
    • Introduce DefaultTimeout and wrap get/set/remove operations in timeout-aware CancellationTokenSource
    • Parallelize Set operations across repositories with Task.WhenAll and count successes
    • Handle OperationCanceledException and TimeoutException with logging
    • Automatically promote items from non-memory repositories into the memory cache asynchronously
    Src/CrispyWaffle/Cache/CacheManager.cs
    Convert ICacheRepository interface to async contract
    • Replace sync Set/Get/TryGet/Remove/TTL/Clear methods with async ValueTask/Task variants
    • Add CancellationToken parameter to all repository interface methods
    Src/CrispyWaffle/Cache/ICacheRepository.cs
    Refactor repository implementations for async, cancellation and robust error handling
    • Implement async Set/Get/TryGet/Remove/TTL/Clear in MemoryCacheRepository with CancellationToken
    • Refactor RedisCacheRepository to ValueTask/Task methods, use Task.WhenAny for cancellation support and propagate exceptions based on configuration
    • Update CouchDBCacheRepository to async/Task methods, add cancellation checks, and unify database resolution to async
    Src/CrispyWaffle/Cache/MemoryCacheRepository.cs
    Src/CrispyWaffle.Redis/Cache/RedisCacheRepository.cs
    Src/CrispyWaffle.CouchDB/Cache/CouchDBCacheRepository.cs
    Adapt unit and integration tests to the new async APIs
    • Change test methods to async Task and await calls to SetAsync/GetAsync/RemoveAsync/TTLAsync
    • Update assertions to use FluentAssertions’ Should().ThrowAsync or Received().SetAsync
    • Use Task.WhenAll in integration tests where multiple async operations are issued
    Tests/CrispyWaffle.Tests/Cache/CacheManagerTests.cs
    Tests/CrispyWaffle.Tests/Cache/MemoryCacheRepositoryTests.cs
    Tests/CrispyWaffle.Tests/Cache/CouchDBCacheRepositoryTests.cs
    Tests/CrispyWaffle.IntegrationTests/Cache/CouchDBCacheRepositoryTests.cs

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @korbit-ai

    korbit-ai Bot commented Jun 27, 2025

    Copy link
    Copy Markdown

    Based on your review schedule, I'll hold off on reviewing this PR until it's marked as ready for review. If you'd like me to take a look now, comment /korbit-review.

    Your admin can change your review schedule in the Korbit Console

    @gstraccini
    gstraccini Bot requested a review from guibranco June 27, 2025 00:54
    @gstraccini gstraccini Bot added the 🚦 awaiting triage Items that are awaiting triage or categorization label Jun 27, 2025
    @coderabbitai

    coderabbitai Bot commented Jun 27, 2025

    Copy link
    Copy Markdown
    Contributor

    Review Change Stack

    Warning

    Review limit reached

    @guibranco, you've reached your PR review limit, so we couldn't start this review.

    Next review available in: 25 minutes

    Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
    You're only billed for reviews past your plan's rate limits ($0.25/file).

    How can I continue?

    After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

    To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

    How do review limits work?

    CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

    For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

    Please refer docs for additional details.

    Review details
    βš™οΈ Run configuration

    Configuration used: Organization UI

    Review profile: CHILL

    Plan: Pro

    Run ID: ec313750-9159-4079-896e-48959897703b

    πŸ“₯ Commits

    Reviewing files that changed from the base of the PR and between e5d61cc and 62f1260.

    πŸ“’ Files selected for processing (14)
    • .config/dotnet-tools.json
    • .github/workflows/build.yml
    • Src/CrispyWaffle.CouchDB/Cache/CouchDBCacheRepository.cs
    • Src/CrispyWaffle.Redis/Cache/RedisCacheRepository.cs
    • Src/CrispyWaffle.Utils/Communications/SmtpMailer.cs
    • Src/CrispyWaffle/Cache/CacheManager.cs
    • Src/CrispyWaffle/Cache/ICacheRepository.cs
    • Src/CrispyWaffle/Cache/MemoryCacheRepository.cs
    • Tests/CrispyWaffle.IntegrationTests/Cache/CouchDBCacheRepositoryTests.cs
    • Tests/CrispyWaffle.Tests/Cache/CacheManagerTests.cs
    • Tests/CrispyWaffle.Tests/Cache/CouchDBCacheRepositoryTests.cs
    • Tests/CrispyWaffle.Tests/Cache/MemoryCacheRepositoryTests.cs
    • Tests/CrispyWaffle.Tests/Scheduler/JobRunnerTests.cs
    • Tests/CrispyWaffle.Tests/TestLogProvider.cs

    Walkthrough

    This change set updates cache manager tests to use asynchronous cache APIs and async exception assertions, and adds a VS Code setting for Snyk organization selection.

    Changes

    Async cache manager tests

    Layer / File(s) Summary
    Async cache manager coverage
    Tests/CrispyWaffle.Tests/Cache/CacheManagerTests.cs
    Adds async test imports, awaits cache calls and mock verifications, and switches exception checks to async assertions across the cache manager test cases.

    Editor settings

    Layer / File(s) Summary
    Snyk organization setting
    .vscode/settings.json
    Adds snyk.advanced.autoSelectOrganization and updates the preceding JSON object closing punctuation.

    Estimated code review effort: 2 (Simple) | ~10 minutes

    Poem

    A bunny typed with hurried paws,
    Async hops now fill the laws.
    Cache tests wait, then leap anew,
    And Snyk gets a setting too.
    Thump! The code now runs with cheer,
    While rabbit ears stay keen and clear.

    πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

    ❌ Failed checks (1 warning)

    Check name Status Explanation Resolution
    Out of Scope Changes check ⚠️ Warning The .vscode/settings.json Snyk/SonarLint config edit is unrelated to the async cache work and appears out of scope. Remove the editor settings change or move it to a separate tooling PR focused on IDE configuration.
    βœ… Passed checks (4 passed)
    Check name Status Explanation
    Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
    Title check βœ… Passed The title clearly summarizes the main change: converting cache manager and repository implementations to async.
    Linked Issues check βœ… Passed The PR implements async cache operations across CacheManager and the listed repositories, with tests updated to match issue #88.
    Docstring Coverage βœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
    ✨ Finishing Touches
    πŸ§ͺ Generate unit tests (beta)
    • Create PR with unit tests

    Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

    ❀️ Share

    Comment @coderabbitai help to get the list of available commands.

    @HarryKambo

    Copy link
    Copy Markdown
    Contributor Author

    Hi I request an early feedback before I update Test cases.

    @AppVeyorBot

    Copy link
    Copy Markdown

    ❌ Build CrispyWaffle 10.0.352 failed (commit 3e9349bd33 by @HarryKambo)

    @AppVeyorBot

    Copy link
    Copy Markdown

    @github-advanced-security

    Copy link
    Copy Markdown

    This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

    @github-advanced-security github-advanced-security AI left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Sonarcsharp (reported by Codacy) found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

    @guibranco

    Copy link
    Copy Markdown
    Owner

    Hi @HarryKambo πŸ‘‹,

    Thank you so much for your pull request! πŸ™Œ

    I appreciate the time and effort you put into this contribution.
    I'll review it shortly, and if everything looks good, I'll approve it as soon as possible.

    Thanks again for your valuable contribution! πŸš€

    @HarryKambo

    Copy link
    Copy Markdown
    Contributor Author

    Hi @guibranco
    Do you want me seperate out the Asycn function into seperate files and restore other. Any changes required kindly let me know.

    @AppVeyorBot

    Copy link
    Copy Markdown

    @guibranco guibranco left a comment

    Copy link
    Copy Markdown
    Owner

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Hi @HarryKambo πŸ‘‹, this PR looks good to me, do you plan to do more work over it? Or it's ready to approve and merge?

    @guibranco guibranco added πŸ“ documentation Tasks related to writing or updating documentation enhancement New feature or request cache 🎲 database Database-related operations .NET Pull requests that update .net code and removed 🚦 awaiting triage Items that are awaiting triage or categorization labels Jul 15, 2025
    @guibranco guibranco changed the title [FEATURE] Update cachemanage and cachereposiotry implementations to async. [FEATURE] Update cachemanager and cacherepository implementations to async Jan 12, 2026
    @AppVeyorBot

    Copy link
    Copy Markdown

    Comment thread Tests/CrispyWaffle.Tests/Cache/CacheManagerTests.cs Fixed
    @gitguardian

    gitguardian Bot commented May 19, 2026

    Copy link
    Copy Markdown

    ⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

    Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

    Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
    Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

    πŸ”Ž Detected hardcoded secret in your pull request
    GitGuardian id GitGuardian status Secret Commit Filename
    13768420 Triggered Generic Password 6f5e4e1 .github/workflows/build.yml View secret
    πŸ›  Guidelines to remediate hardcoded secrets
    1. Understand the implications of revoking this secret by investigating where it is used in your code.
    2. Replace and store your secret safely. Learn here the best practices.
    3. Revoke and rotate this secret.
    4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

    To avoid such incidents in the future consider


    πŸ¦‰ GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

    @guibranco

    Copy link
    Copy Markdown
    Owner

    @gstraccini csharpier

    @gstraccini

    gstraccini Bot commented Jul 4, 2026

    Copy link
    Copy Markdown
    Contributor

    Running CSharpier on this branch! πŸ”§

    @gstraccini

    gstraccini Bot commented Jul 4, 2026

    Copy link
    Copy Markdown
    Contributor

    ❌ CSharpier failed!

    guibranco added 2 commits July 4, 2026 03:55
    Modify CacheManager to use TryGetAsync for retrieving cache items,
    enabling operations to return success status along with the cached
    value. This refactoring improves error handling and ensures that
    exceptions only occur when explicitly thrown, such as during
    timeouts or cancellation. Update tests to reflect new TTL handling
    and remove redundant operations. Enhance settings.json to autoselect
    organization for Snyk, ensuring configurations are aligned for
    automation and security purposes.

    @coderabbitai coderabbitai Bot left a comment

    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Caution

    Some comments are outside the diff and can’t be posted inline due to platform limitations.

    ⚠️ Outside diff range comments (5)
    Tests/CrispyWaffle.Tests/Cache/CacheManagerTests.cs (1)

    147-163: 🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

    Test won't actually assert the exception β€” missing async Task + await.

    The method is still public void, and act.Should().ThrowAsync<InvalidOperationException>().WithMessage(...) returns an unawaited Task. Since the test method itself isn't async and doesn't await this task, the assertion runs fire-and-forget after the test method returns β€” the test can report as passing even if the thrown exception/message doesn't match.

    πŸ› Proposed fix
    -    public void RemoveFromShouldThrowWhenRepositoryNotFound()
    +    public async Task RemoveFromShouldThrowWhenRepositoryNotFound()
         {
             // Arrange
             var key = "testKey";
             CacheManager.AddRepository(_mockCacheRepository);
     
             // Act
             Func<Task> act = async () => await CacheManager.RemoveFrom<ICacheRepository>(key);
     
             // Assert
    -        act.Should()
    +        await act.Should()
                 .ThrowAsync<InvalidOperationException>()
                 .WithMessage(
                     "The repository of type CrispyWaffle.Cache.ICacheRepository isn't available in the repositories providers list"
                 );
         }
    πŸ€– Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@Tests/CrispyWaffle.Tests/Cache/CacheManagerTests.cs` around lines 147 - 163,
    The test in CacheManagerTests.RemoveFromShouldThrowWhenRepositoryNotFound is not
    awaiting the async exception assertion, so it may pass without validating the
    thrown InvalidOperationException. Change the test method to return an async Task
    and await the ThrowAsync/WithMessage assertion on act so the exception and
    message are actually verified. Use the existing RemoveFrom<ICacheRepository> and
    CacheManager.AddRepository setup to keep the test behavior the same.
    
    Src/CrispyWaffle/Cache/CacheManager.cs (4)

    1008-1012: πŸ—„οΈ Data Integrity & Integration | 🟠 Major | ⚑ Quick win

    Removal failures are swallowed with no logging at all.

    Unlike the equivalent catch blocks in GetAsync/TryGetAsync (e.g. Lines 524-530), the generic catch (Exception ex) here only appends to the local errors list β€” there is no LogConsumer.Error call:

    catch (Exception ex)
    {
        var repositoryName = repository.GetType().Name;
        errors.Add((repositoryName, ex));
    }

    Since errors is never read after the loop, a failed removal from a repository (e.g. Redis timeout, CouchDB conflict) leaves stale data behind with zero trace in logs, and the method returns as if the removal succeeded everywhere.

    πŸ› Proposed fix
             catch (Exception ex)
             {
                 var repositoryName = repository.GetType().Name;
                 errors.Add((repositoryName, ex));
    +            LogConsumer.Error("Failed to remove key {0} from repository {1}: {2}", key, repositoryName, ex.Message);
             }

    Also applies to: 1068-1072

    πŸ€– Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@Src/CrispyWaffle/Cache/CacheManager.cs` around lines 1008 - 1012, Removal
    failures in CacheManager are being swallowed silently in the repository loop, so
    add a LogConsumer.Error call inside the catch block in the removal path(s) while
    keeping the existing errors list update if needed. Use the same logging pattern
    as GetAsync/TryGetAsync and include the repositoryName plus exception details so
    failed removals are visible. Apply this fix to both removal catch blocks in
    CacheManager to cover all paths.
    

    261-291: 🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

    Cancellation is silently swallowed in all SetToAsync overloads, contradicting the documented contract.

    Each overload's XML doc promises <exception cref="OperationCanceledException">If cancelled.</exception> (Lines 259, 303, 348, 399), but the implementation catches OperationCanceledException and just logs to console without rethrowing:

    catch (OperationCanceledException)
    {
        Console.WriteLine("Cache operation was cancelled");
    }

    A caller that cancels the token gets a normally-completed ValueTask as if the write succeeded β€” this can mask cancellation-driven aborts in calling code (e.g. pipelines that rely on OperationCanceledException to unwind).

    πŸ› Proposed fix (repeat for all four overloads)
             catch (OperationCanceledException)
             {
    -            Console.WriteLine("Cache operation was cancelled");
    +            LogConsumer.Trace("Cache operation was cancelled");
    +            throw;
             }

    Also applies to: 305-336, 350-386, 401-439

    πŸ€– Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@Src/CrispyWaffle/Cache/CacheManager.cs` around lines 261 - 291, The
    SetToAsync overloads in CacheManager are swallowing OperationCanceledException
    instead of honoring the documented cancellation contract. Update each
    SetToAsync<TCacheRepository, TValue> overload to let cancellation propagate from
    repository.SetAsync by removing the special catch block or rethrowing the
    cancellation exception after any optional logging, and keep the behavior
    consistent across all overloads. Use the existing CacheManager.SetToAsync
    methods and the repository.SetAsync call as the main locations to apply the fix.
    

    647-647: πŸ“ Maintainability & Code Quality | 🟠 Major | ⚑ Quick win

    Add Async suffix to the remaining public cache APIs The non-suffixed async overloads (GetFrom, Remove, RemoveFrom) break the naming pattern used elsewhere in CacheManager and make these entry points look synchronous. Rename them to GetFromAsync, RemoveAsync, and RemoveFromAsync to keep the public API consistent.

    πŸ€– Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@Src/CrispyWaffle/Cache/CacheManager.cs` at line 647, The remaining public
    async cache APIs in CacheManager are missing the Async suffix, which makes them
    inconsistent with the rest of the public surface. Rename the async overloads
    GetFrom, Remove, and RemoveFrom to GetFromAsync, RemoveAsync, and
    RemoveFromAsync, and update any related references/usages so the public API
    naming stays consistent and clearly asynchronous.
    

    401-439: 🎯 Functional Correctness | 🟠 Major | ⚑ Quick win

    Forward ttl in this overload.

    subKey is already passed through, but ttl is discarded because the call only uses SetAsync(value, key, subKey, ...). ICacheRepository has no combined (key, subKey, ttl) overload, so callers of this method will never get expiration unless a new repository API is added. The trace format also uses {2} twice, so subKey is omitted from the log.

    πŸ€– Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@Src/CrispyWaffle/Cache/CacheManager.cs` around lines 401 - 439, The
    SetToAsync<TCacheRepository, TValue> overload is dropping the ttl argument and
    the trace message is also formatting the parameters incorrectly, so expiration
    and subKey visibility are lost. Update CacheManager.SetToAsync to propagate ttl
    through to the repository call by using the appropriate repository API or adding
    one if needed, and fix the LogConsumer.Trace argument order so key, repository
    type, ttl, and subKey are all logged correctly. Keep the existing cancellation
    and exception handling intact.
    
    🧹 Nitpick comments (3)
    .vscode/settings.json (1)

    22-26: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | πŸ’€ Low value

    Unrelated editor setting change bundled into async cache PR.

    This Snyk organization setting is unrelated to the async cache manager feature described in the PR objectives. Consider moving it to a separate PR to keep the diff focused.

    πŸ€– Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In @.vscode/settings.json around lines 22 - 26, The async cache PR includes an
    unrelated editor configuration change in the VS Code settings. Remove the Snyk
    organization setting from this change set and keep the editor-only update
    separate from the async cache work; use the existing settings entries in the
    .vscode/settings.json diff as the place to locate and split it out.
    
    Src/CrispyWaffle/Cache/CacheManager.cs (2)

    466-466: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

    Collected errors are never surfaced to the caller.

    GetAsync (x2), TryGetAsync (x2), and Remove (x2) all build a local errors list during the repository loop, but it's discarded once the loop ends β€” the final InvalidOperationException("Unable to get the item with key {key}") (e.g. Line 539) includes no detail about what actually failed in each repository.

    Consider aggregating errors into the thrown exception (e.g. via AggregateException as InnerException, or appended to the message) so failures are diagnosable without re-enabling debug logging.

    Also applies to: 562-562, 760-760, 860-860, 981-981, 1041-1041

    πŸ€– Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@Src/CrispyWaffle/Cache/CacheManager.cs` at line 466, The local errors list
    built in CacheManager methods like GetAsync, TryGetAsync, and Remove is
    discarded before the final exception is thrown, so the caller gets no repository
    failure details. Update the final throw sites in these methods to include the
    collected errors, either by wrapping them in an
    AggregateException/InnerException or by appending repository names and exception
    messages to the existing InvalidOperationException text. Use the existing errors
    list and the repository loop variables in CacheManager to locate each affected
    throw path.
    

    941-961: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

    TTLAsync doesn't accept a CancellationToken, unlike every other public method in this class.

    Given the PR's goal of adding cancellation-token support across cache operations, this method is the one outlier without it.

    πŸ€– Prompt for AI Agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    In `@Src/CrispyWaffle/Cache/CacheManager.cs` around lines 941 - 961, TTLAsync is
    the remaining public cache operation that does not accept a CancellationToken.
    Update the CacheManager.TTLAsync signature to take a CancellationToken, then
    thread it through each repository.TTLAsync call so cancellation is honored
    consistently with the other public methods. Keep the existing TTL aggregation
    logic and adjust any callers or interface implementations that reference
    TTLAsync.
    
    πŸ€– Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Outside diff comments:
    In `@Src/CrispyWaffle/Cache/CacheManager.cs`:
    - Around line 1008-1012: Removal failures in CacheManager are being swallowed
    silently in the repository loop, so add a LogConsumer.Error call inside the
    catch block in the removal path(s) while keeping the existing errors list update
    if needed. Use the same logging pattern as GetAsync/TryGetAsync and include the
    repositoryName plus exception details so failed removals are visible. Apply this
    fix to both removal catch blocks in CacheManager to cover all paths.
    - Around line 261-291: The SetToAsync overloads in CacheManager are swallowing
    OperationCanceledException instead of honoring the documented cancellation
    contract. Update each SetToAsync<TCacheRepository, TValue> overload to let
    cancellation propagate from repository.SetAsync by removing the special catch
    block or rethrowing the cancellation exception after any optional logging, and
    keep the behavior consistent across all overloads. Use the existing
    CacheManager.SetToAsync methods and the repository.SetAsync call as the main
    locations to apply the fix.
    - Line 647: The remaining public async cache APIs in CacheManager are missing
    the Async suffix, which makes them inconsistent with the rest of the public
    surface. Rename the async overloads GetFrom, Remove, and RemoveFrom to
    GetFromAsync, RemoveAsync, and RemoveFromAsync, and update any related
    references/usages so the public API naming stays consistent and clearly
    asynchronous.
    - Around line 401-439: The SetToAsync<TCacheRepository, TValue> overload is
    dropping the ttl argument and the trace message is also formatting the
    parameters incorrectly, so expiration and subKey visibility are lost. Update
    CacheManager.SetToAsync to propagate ttl through to the repository call by using
    the appropriate repository API or adding one if needed, and fix the
    LogConsumer.Trace argument order so key, repository type, ttl, and subKey are
    all logged correctly. Keep the existing cancellation and exception handling
    intact.
    
    In `@Tests/CrispyWaffle.Tests/Cache/CacheManagerTests.cs`:
    - Around line 147-163: The test in
    CacheManagerTests.RemoveFromShouldThrowWhenRepositoryNotFound is not awaiting
    the async exception assertion, so it may pass without validating the thrown
    InvalidOperationException. Change the test method to return an async Task and
    await the ThrowAsync/WithMessage assertion on act so the exception and message
    are actually verified. Use the existing RemoveFrom<ICacheRepository> and
    CacheManager.AddRepository setup to keep the test behavior the same.
    
    ---
    
    Nitpick comments:
    In @.vscode/settings.json:
    - Around line 22-26: The async cache PR includes an unrelated editor
    configuration change in the VS Code settings. Remove the Snyk organization
    setting from this change set and keep the editor-only update separate from the
    async cache work; use the existing settings entries in the .vscode/settings.json
    diff as the place to locate and split it out.
    
    In `@Src/CrispyWaffle/Cache/CacheManager.cs`:
    - Line 466: The local errors list built in CacheManager methods like GetAsync,
    TryGetAsync, and Remove is discarded before the final exception is thrown, so
    the caller gets no repository failure details. Update the final throw sites in
    these methods to include the collected errors, either by wrapping them in an
    AggregateException/InnerException or by appending repository names and exception
    messages to the existing InvalidOperationException text. Use the existing errors
    list and the repository loop variables in CacheManager to locate each affected
    throw path.
    - Around line 941-961: TTLAsync is the remaining public cache operation that
    does not accept a CancellationToken. Update the CacheManager.TTLAsync signature
    to take a CancellationToken, then thread it through each repository.TTLAsync
    call so cancellation is honored consistently with the other public methods. Keep
    the existing TTL aggregation logic and adjust any callers or interface
    implementations that reference TTLAsync.
    

    ℹ️ Review info
    βš™οΈ Run configuration

    Configuration used: Organization UI

    Review profile: CHILL

    Plan: Pro

    Run ID: 583fbaa9-f3da-48a8-be8e-97c93fbcb493

    πŸ“₯ Commits

    Reviewing files that changed from the base of the PR and between 3af3711 and e5d61cc.

    πŸ“’ Files selected for processing (3)
    • .vscode/settings.json
    • Src/CrispyWaffle/Cache/CacheManager.cs
    • Tests/CrispyWaffle.Tests/Cache/CacheManagerTests.cs

    guibranco added 3 commits July 4, 2026 04:15
    Replace direct calls to `_testOutputHelper.WriteLine` with a new
    `SafeWriteLine` method. This change addresses the issue of
    `InvalidOperationException` being thrown when test output occurs
    after the test has completed. `SafeWriteLine` wraps the write
    operations in a try-catch block to safely ignore these exceptions,
    ensuring more robust handling of log output during parallel test
    execution.
    Add conditional checks to prevent executing SonarCloud and Codecov steps
    on pull requests from forks. This ensures that sensitive information
    (such as tokens) is only used when the head repository matches the main
    repository, thus enhancing security by avoiding exposure on external PRs.
    Change the concurrency test's task delay from async Task.Delay to
    Thread.Sleep. This modification improves the test reliability
    by ensuring precise execution timing, as Task.Delay could be
    subject to task scheduler variations, affecting the accuracy
    of concurrency measurements.
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    cache ♻️ code quality Code quality-related tasks or issues communications CouchDB CouchDB related issues/pull requests 🎲 database Database-related operations πŸ“ documentation Tasks related to writing or updating documentation enhancement New feature or request gitauto GitAuto label to trigger the app in a issue. hacktoberfest Participation in the Hacktoberfest event πŸ•” high effort A task that can be completed in a few days .NET Pull requests that update .net code performance Redis Review effort [1-5]: 4 πŸ§‘β€πŸ’» tech-debit Technical debt that needs to be addressed πŸ§ͺ tests Tasks related to testing ⏳ waiting response Waiting on a response from another party

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    [FEATURE] Add async operations for CacheManager and ICacheRepository classes

    5 participants